/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.im.server.service.ws.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.iwindplus.boot.domain.constant.CommonConstant;
import com.iwindplus.boot.util.JacksonUtil;
import com.iwindplus.boot.websocket.service.WebSocketServerBootstrap;
import com.iwindplus.im.domain.dto.AddFriendMsgDTO;
import com.iwindplus.im.domain.dto.DirectMsgDTO;
import com.iwindplus.im.domain.dto.FriendChatMsgDTO;
import com.iwindplus.im.domain.dto.GroupChatMsgDTO;
import com.iwindplus.im.domain.dto.SysNoticeMsgDTO;
import com.iwindplus.im.domain.dto.WsSendMsgDTO;
import com.iwindplus.im.domain.enums.CommandEnum;
import com.iwindplus.im.domain.enums.MsgTypeEnum;
import com.iwindplus.im.domain.enums.SendStatusEnum;
import com.iwindplus.im.domain.vo.AddFriendMsgVO;
import com.iwindplus.im.domain.vo.DirectMsgVO;
import com.iwindplus.im.domain.vo.FriendChatMsgVO;
import com.iwindplus.im.server.model.AddFriendMsgDO;
import com.iwindplus.im.server.model.DirectMsgDO;
import com.iwindplus.im.server.model.FriendChatMsgDO;
import com.iwindplus.im.server.model.GroupChatMsgDO;
import com.iwindplus.im.server.model.SysNoticeMsgDO;
import com.iwindplus.im.server.service.AddFriendMsgService;
import com.iwindplus.im.server.service.DirectMsgService;
import com.iwindplus.im.server.service.FriendChatMsgService;
import com.iwindplus.im.server.service.GroupChatMsgService;
import com.iwindplus.im.server.service.SysNoticeMsgService;
import com.iwindplus.im.server.service.ws.WsMsgService;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.TioConfig;
import org.tio.utils.lock.SetWithLock;
import org.tio.websocket.common.WsPacket;
import org.tio.websocket.common.WsResponse;

/**
 * websocket 业务层接口实现类.
 *
 * @author zengdegui
 * @since 2023/12/04 23:22
 */
@Service
@Slf4j
public class WsMsgServiceImpl implements WsMsgService {

    @Resource
    private WebSocketServerBootstrap webSocketServerBootstrap;

    @Resource
    private DirectMsgService directMsgService;

    @Resource
    private FriendChatMsgService friendChatMsgService;

    @Resource
    private GroupChatMsgService groupChatMsgService;

    @Resource
    private SysNoticeMsgService sysNoticeMsgService;

    @Resource
    private AddFriendMsgService addFriendMsgService;

    @Async
    @Override
    public void sendWsMsg(WsSendMsgDTO entity, ChannelContext channelContext) {
        final CommandEnum command = entity.getCommand();
        if (CommandEnum.HEARTBEAT.equals(command)) {
            this.sendHeartbeat(entity, channelContext);
        } else if (CommandEnum.ACCESS_TOKEN_EXPIRED_MSG.equals(command)) {
            // 访问token过期消息.
            this.sendAccessTokenExpiredMsg(entity, channelContext);
        } else if (CommandEnum.REFRESH_PERMISSION_MSG.equals(command)) {
            // 刷新权限消息.
            this.sendRefreshPermissionMsg(entity, channelContext);
        } else if (CommandEnum.DIRECT_MSG.equals(command)) {
            // 直发消息.
            this.sendDirectMsg(entity, channelContext);
        } else if (CommandEnum.SYS_NOTICE_MSG.equals(command)) {
            // 发送系统通知.
            this.sendSysNoticeMsg(entity, channelContext);
        } else if (CommandEnum.FRIEND_CHAT_MSG.equals(command)) {
            // 发送好友聊天（单聊）.
            this.sendFriendChatMsg(entity, channelContext);
        } else if (CommandEnum.GROUP_CHAT_MSG.equals(command)) {
            // 发送群聊.
            this.sendGroupChatMsg(entity, channelContext);
        } else if (CommandEnum.GROUP_CHAT_NOTICE_MSG.equals(command)) {
            // 发送群聊通知.
            this.sendGroupChatNoticeMsg(entity, channelContext);
        } else if (CommandEnum.ADD_FRIEND_MSG.equals(command)) {
            // 发送加好友消息.
            this.sendAddFriendMsg(entity, channelContext);
        } else if (CommandEnum.OFFLINE_DIRECT_MSG.equals(command)) {
            // 发送离线直发消息.
            this.sendOfflineDirectMsg(entity, channelContext);
        } else if (CommandEnum.OFFLINE_FRIEND_CHAT_MSG.equals(command)) {
            // 发送离线好友聊天消息.
            this.sendOfflineFriendChatMsg(entity, channelContext);
        } else if (CommandEnum.OFFLINE_ADD_FRIEND_MSG.equals(command)) {
            // 发送离线加好友消息.
            this.sendOfflineAddFriendMsg(entity, channelContext);
        }
    }

    private void sendHeartbeat(WsSendMsgDTO msg, ChannelContext channelContext) {
        String text = JacksonUtil.toJsonStr(msg);
        final WsResponse wsResponse = WsResponse.fromText(text, WsPacket.CHARSET_NAME);
        Tio.send(channelContext, wsResponse);
    }

    private void sendAccessTokenExpiredMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        this.sendToUserMsg(msg, channelContext);
    }

    private void sendRefreshPermissionMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        this.sendToUserMsg(msg, channelContext);
    }

    private Boolean sendToUserMsg(WsSendMsgDTO msg, TioConfig tioConfig) {
        String text = JacksonUtil.toJsonStr(msg);
        final WsResponse wsResponse = WsResponse.fromText(text, WsPacket.CHARSET_NAME);
        return Tio.sendToUser(tioConfig, msg.getReceiverId().toString(), wsResponse);
    }

    private Boolean sendToUserMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        final TioConfig tioConfig = this.getTioConfig(channelContext);
        SetWithLock<ChannelContext> channelContextSetWithLock = Tio.getByUserid(tioConfig, msg.getReceiverId().toString());
        if (Objects.nonNull(channelContextSetWithLock) && 0 < channelContextSetWithLock.size()) {
            return this.sendToUserMsg(msg, tioConfig);
        }
        return false;
    }

    private void sendDirectMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        DirectMsgDTO param = BeanUtil.copyProperties(msg, DirectMsgDTO.class);
        param.setSenderId(msg.getSendUserId());
        param.setOrgId(msg.getSendOrgId());
        param.setReceiverId(msg.getReceiverId());
        param.setSendStatus(SendStatusEnum.TO_BE_SENT);
        this.directMsgService.save(param);

        final TioConfig tioConfig = this.getTioConfig(channelContext);
        SetWithLock<ChannelContext> channelContextSetWithLock = Tio.getByUserid(tioConfig, msg.getReceiverId().toString());
        if (Objects.nonNull(channelContextSetWithLock) && 0 < channelContextSetWithLock.size()) {
            DirectMsgDO entity = DirectMsgDO.builder()
                .id(param.getId())
                .sendTime(LocalDateTime.now())
                .build();

            msg.setMsgId(param.getId());
            final Boolean flag = this.sendToUserMsg(msg, tioConfig);
            if (Boolean.TRUE.equals(flag)) {
                entity.setSendStatus(SendStatusEnum.SUCCESS);
            } else {
                entity.setSendStatus(SendStatusEnum.FAILED);
            }
            this.directMsgService.updateById(entity);
        }
    }

    private void sendSysNoticeMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        SysNoticeMsgDTO param = BeanUtil.copyProperties(msg, SysNoticeMsgDTO.class);
        param.setSenderId(msg.getSendUserId());
        param.setOrgId(msg.getSendOrgId());
        param.setSendStatus(SendStatusEnum.TO_BE_SENT);
        this.sysNoticeMsgService.save(param);

        final TioConfig tioConfig = this.getTioConfig(channelContext);
        SetWithLock<ChannelContext> channelContextSetWithLock = Tio.getAll(tioConfig);
        if (Objects.nonNull(channelContextSetWithLock) && 0 < channelContextSetWithLock.size()) {
            SysNoticeMsgDO entity = SysNoticeMsgDO.builder()
                .id(param.getId())
                .sendTime(LocalDateTime.now())
                .build();

            msg.setMsgId(param.getId());
            String text = JacksonUtil.toJsonStr(msg);
            final WsResponse wsResponse = WsResponse.fromText(text, WsPacket.CHARSET_NAME);
            try {
                Tio.sendToAll(tioConfig, wsResponse);
                entity.setSendStatus(SendStatusEnum.SUCCESS);
            } catch (Exception ex) {
                log.warn(CommonConstant.ExceptionConstant.EXCEPTION, ex);
                entity.setSendStatus(SendStatusEnum.FAILED);
            }
            this.sysNoticeMsgService.updateById(entity);
        }
    }

    private void sendFriendChatMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        FriendChatMsgDTO param = BeanUtil.copyProperties(msg, FriendChatMsgDTO.class);
        param.setSenderId(msg.getSendUserId());
        param.setOrgId(msg.getSendOrgId());
        param.setReceiverId(msg.getReceiverId());
        param.setSendStatus(SendStatusEnum.TO_BE_SENT);
        this.friendChatMsgService.save(param);

        final TioConfig tioConfig = this.getTioConfig(channelContext);
        SetWithLock<ChannelContext> channelContextSetWithLock = Tio.getByUserid(tioConfig, msg.getReceiverId().toString());
        if (Objects.nonNull(channelContextSetWithLock) && 0 < channelContextSetWithLock.size()) {
            FriendChatMsgDO entity = FriendChatMsgDO.builder()
                .id(param.getId())
                .sendTime(LocalDateTime.now())
                .build();

            msg.setMsgId(param.getId());
            final Boolean flag = this.sendToUserMsg(msg, tioConfig);
            if (Boolean.TRUE.equals(flag)) {
                entity.setSendStatus(SendStatusEnum.SUCCESS);
            } else {
                entity.setSendStatus(SendStatusEnum.FAILED);
            }
            this.friendChatMsgService.updateById(entity);
        }
    }

    private void sendGroupChatMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        GroupChatMsgDTO param = BeanUtil.copyProperties(msg, GroupChatMsgDTO.class);
        param.setSenderId(msg.getSendUserId());
        param.setOrgId(msg.getSendOrgId());
        param.setSendStatus(SendStatusEnum.TO_BE_SENT);
        this.groupChatMsgService.save(param);

        final TioConfig tioConfig = this.getTioConfig(channelContext);
        SetWithLock<ChannelContext> channelContextSetWithLock = Tio.getByGroup(tioConfig, msg.getReceiverId().toString());
        if (Objects.nonNull(channelContextSetWithLock) && 0 < channelContextSetWithLock.size()) {
            GroupChatMsgDO entity = GroupChatMsgDO.builder()
                .id(param.getId())
                .sendTime(LocalDateTime.now())
                .build();

            msg.setMsgId(param.getId());
            String text = JacksonUtil.toJsonStr(msg);
            final WsResponse wsResponse = WsResponse.fromText(text, WsPacket.CHARSET_NAME);
            try {
                Tio.sendToGroup(tioConfig, msg.getReceiverId().toString(), wsResponse);
                entity.setSendStatus(SendStatusEnum.SUCCESS);
            } catch (Exception ex) {
                log.warn(CommonConstant.ExceptionConstant.EXCEPTION, ex);
                entity.setSendStatus(SendStatusEnum.FAILED);
            }
            this.groupChatMsgService.updateById(entity);
        }
    }

    private void sendGroupChatNoticeMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        final TioConfig tioConfig = this.getTioConfig(channelContext);
        SetWithLock<ChannelContext> channelContextSetWithLock = Tio.getByGroup(tioConfig, msg.getReceiverId().toString());
        if (Objects.nonNull(channelContextSetWithLock) && 0 < channelContextSetWithLock.size()) {
            String text = JacksonUtil.toJsonStr(msg);
            final WsResponse wsResponse = WsResponse.fromText(text, WsPacket.CHARSET_NAME);
            Tio.sendToGroup(tioConfig, msg.getReceiverId().toString(), wsResponse);
        }
    }

    private void sendAddFriendMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        AddFriendMsgDTO param = BeanUtil.copyProperties(msg, AddFriendMsgDTO.class);
        param.setSenderId(msg.getSendUserId());
        param.setOrgId(msg.getSendOrgId());
        param.setReceiverId(msg.getReceiverId());
        param.setSendStatus(SendStatusEnum.TO_BE_SENT);
        this.addFriendMsgService.save(param);

        final TioConfig tioConfig = this.getTioConfig(channelContext);
        SetWithLock<ChannelContext> channelContextSetWithLock = Tio.getByUserid(tioConfig, msg.getReceiverId().toString());
        if (Objects.nonNull(channelContextSetWithLock) && 0 < channelContextSetWithLock.size()) {
            AddFriendMsgDO entity = AddFriendMsgDO.builder()
                .id(param.getId())
                .sendTime(LocalDateTime.now())
                .build();

            msg.setMsgId(param.getId());
            final Boolean flag = this.sendToUserMsg(msg, tioConfig);
            if (Boolean.TRUE.equals(flag)) {
                entity.setSendStatus(SendStatusEnum.SUCCESS);
            } else {
                entity.setSendStatus(SendStatusEnum.FAILED);
            }
            this.addFriendMsgService.updateById(entity);
        }
    }

    private void sendOfflineDirectMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        final TioConfig tioConfig = this.getTioConfig(channelContext);

        final List<DirectMsgVO> list = this.directMsgService.listByUnSendSuccess(msg.getSendUserId(), msg.getSendOrgId());
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(data -> this.offlineDirectMsg(tioConfig, msg, data));
        }
    }

    private void sendOfflineFriendChatMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        final TioConfig tioConfig = this.getTioConfig(channelContext);

        final List<FriendChatMsgVO> list = this.friendChatMsgService.listByUnSendSuccess(msg.getSendUserId(), msg.getSendOrgId());
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(data -> this.offlineFriendChatMsg(tioConfig, msg, data));
        }
    }

    private void sendOfflineAddFriendMsg(WsSendMsgDTO msg, ChannelContext channelContext) {
        final TioConfig tioConfig = this.getTioConfig(channelContext);

        final List<AddFriendMsgVO> list = this.addFriendMsgService.listByUnSendSuccess(msg.getSendUserId(), msg.getSendOrgId());
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(data -> this.offlineAddFriendMsg(tioConfig, msg, data));
        }
    }

    private void offlineDirectMsg(TioConfig tioConfig, WsSendMsgDTO wsMsg, DirectMsgVO entity) {
        final WsSendMsgDTO msg = WsSendMsgDTO.builder()
            .command(wsMsg.getCommand())
            .msgType(entity.getMsgType())
            .title(entity.getTitle())
            .content(entity.getContent())
            .sendUserId(wsMsg.getSendUserId())
            .receiverId(wsMsg.getReceiverId())
            .msgId(entity.getId())
            .build();

        DirectMsgDO param = DirectMsgDO.builder()
            .id(entity.getId())
            .sendTime(LocalDateTime.now())
            .build();

        final Boolean flag = this.sendToUserMsg(msg, tioConfig);

        if (Boolean.TRUE.equals(flag)) {
            param.setSendStatus(SendStatusEnum.SUCCESS);
        } else {
            param.setSendStatus(SendStatusEnum.FAILED);
        }
        this.directMsgService.updateById(param);
    }

    private void offlineFriendChatMsg(TioConfig tioConfig, WsSendMsgDTO wsMsg, FriendChatMsgVO entity) {
        final WsSendMsgDTO msg = WsSendMsgDTO.builder()
            .command(wsMsg.getCommand())
            .msgType(entity.getMsgType())
            .content(entity.getContent())
            .sendUserId(wsMsg.getSendUserId())
            .receiverId(wsMsg.getReceiverId())
            .msgId(entity.getId())
            .build();

        FriendChatMsgDO param = FriendChatMsgDO.builder()
            .id(entity.getId())
            .sendTime(LocalDateTime.now())
            .build();

        final Boolean flag = this.sendToUserMsg(msg, tioConfig);

        if (Boolean.TRUE.equals(flag)) {
            param.setSendStatus(SendStatusEnum.SUCCESS);
        } else {
            param.setSendStatus(SendStatusEnum.FAILED);
        }
        this.friendChatMsgService.updateById(param);
    }

    private void offlineAddFriendMsg(TioConfig tioConfig, WsSendMsgDTO wsMsg, AddFriendMsgVO entity) {
        final WsSendMsgDTO msg = WsSendMsgDTO.builder()
            .command(wsMsg.getCommand())
            .msgType(MsgTypeEnum.TEXT)
            .content(entity.getContent())
            .sendUserId(wsMsg.getSendUserId())
            .receiverId(wsMsg.getReceiverId())
            .msgId(entity.getId())
            .build();

        AddFriendMsgDO param = AddFriendMsgDO.builder()
            .id(entity.getId())
            .sendTime(LocalDateTime.now())
            .build();

        final Boolean flag = this.sendToUserMsg(msg, tioConfig);

        if (Boolean.TRUE.equals(flag)) {
            param.setSendStatus(SendStatusEnum.SUCCESS);
        } else {
            param.setSendStatus(SendStatusEnum.FAILED);
        }
        this.addFriendMsgService.updateById(param);
    }

    private TioConfig getTioConfig(ChannelContext channelContext) {
        TioConfig tioConfig;
        if (Objects.nonNull(channelContext)) {
            tioConfig = channelContext.getTioConfig();
        } else {
            tioConfig = this.webSocketServerBootstrap.getServerTioConfig();
        }
        return tioConfig;
    }
}
